﻿using System;
using System.Threading;
using System.Windows.Input;
using WpfStarterKit.Framework.Navigation;

namespace WpfStarterKit.Framework.Commands
{
    /// <summary>
    /// The RelayCommand class allows the instantiator of the class to set up arbitrary code that can be executed when the
    /// command is executed. Effectively, it relays the command's execute event to whatever method the instantiator chooses.
    /// </summary>
    public class RelayCommand : ICommand
    {
        /// <summary>
        /// Handler run when the command is executed
        /// </summary>
        protected Action<object> ExecuteHandler { get; set; }

        /// <summary>
        /// Handler for the method that checks whether the command can be executed
        /// </summary>
        protected Predicate<object> CanExecuteHandler { get; set; }

        /// <summary>
        /// Wait overlay attribute for the execute delegate
        /// </summary>
        protected LongRunningOperationAttribute Wait { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="execute">Method to execute when the command is triggered</param>
        public RelayCommand(Action<object> execute)
            : this(execute, null)
        {
        }

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="execute">Method to execute when the command is triggered</param>
        /// <param name="canExecute">Predicate that determines whether the command can be executed</param>
        public RelayCommand(Action<object> execute, Predicate<object> canExecute)
        {
            if (execute != null)
            {
                Wait = LongRunningOperationAttribute.GetAttribute(execute.Method);
            }

            ExecuteHandler = execute;
            CanExecuteHandler = canExecute;
        }

        #region ICommand Members
        /// <summary>
        /// CanExecute implementation of the command
        /// </summary>
        /// <param name="parameter">Command parameter provided by the command source</param>
        /// <returns>true if command is ready for execution, false otherwise</returns>
        public virtual bool CanExecute(object parameter)
        {
            return RunCanExecutePredicate(parameter);
        }

        /// <summary>
        /// Triggered when the value of CanExecute changes
        /// </summary>
        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }

        /// <summary>
        /// Execute the command. Calls the execute delegate passed into the constructor
        /// </summary>
        /// <param name="parameter">Command parameter provided by the command source</param>
        public virtual void Execute(object parameter)
        {
            RunExecuteHandler(parameter);
        }

        #endregion

        /// <summary>
        /// Runs the CanExecute predicate if it is non-null. Returns true if a CanExecute predicate isn't defined.
        /// </summary>
        /// <param name="parameter">Command parameter</param>
        /// <returns>true if the CanExecute predicate doesn't exist or if it evaluated to true, false otherwise</returns>
        protected bool RunCanExecutePredicate(object parameter)
        {
            if (CanExecuteHandler != null)
            {
                try
                {
                    return CanExecuteHandler(parameter);
                }
                catch (ThreadAbortException threadAbortException)
                {
                    ApplicationManager.Instance.ExceptionHandler.HandleException(threadAbortException, CanExecuteHandler, false, GetType());
                    throw;
                }
                catch (Exception exception)
                {
                    ApplicationManager.Instance.ExceptionHandler.HandleException(exception, CanExecuteHandler, true, GetType());
                }
            }

            return true;
        }

        /// <summary>
        /// Runs the Execute handler is one exists.
        /// </summary>
        /// <param name="parameter">Command parameter</param>
        protected void RunExecuteHandler(object parameter)
        {
            if (ExecuteHandler != null)
            {
                try
                {
                    if (Wait != null)
                    {
                        LongRunningOperationExecutionManager manager = new LongRunningOperationExecutionManager(Wait, ExecuteHandler, parameter);
                        manager.Execute();
                    }
                    else
                    {
                        ExecuteHandler(parameter);
                    }
                }
                catch (ThreadAbortException threadAbortException)
                {
                    ApplicationManager.Instance.ExceptionHandler.HandleException(threadAbortException, ExecuteHandler, false, GetType());
                    throw;
                }
                catch (Exception exception)
                {
                    ApplicationManager.Instance.ExceptionHandler.HandleException(exception, ExecuteHandler, true, GetType());
                }
            }
        }
    }
}
